/*
 * Copyright (c) 2007 jNetX.
 * http://www.jnetx.com
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * jNetX. You shall not disclose such Confidential Information and
 * shall use it only in accordance with the terms of the license
 * agreement you entered into with jNetX.
 *
 * $Id$
 */
package com.jnetx.javax.sip.address;

import javax.sip.address.URI;
import javax.sip.header.Parameters;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author <a href="mailto:dparhonin@jnetx.ru">Dmitry Parhonin</a>
 * @version $Revision$
 */
public abstract class URIBase implements URI, Parameters {
    private final Map<String, String> parameters = new HashMap<String, String>();
    private Map<String, String> headers;
    private String scheme;

    protected byte[] data;
    protected int start, end;
    protected String stringValue;
    protected boolean parsed = true;

    protected URIBase(String scheme) {
        this.scheme = scheme;
    }

    public String getScheme() {
        return scheme;
    }

    protected void setScheme(String scheme) {
        stringValue = null; //invalidateStringValue();
        this.scheme = scheme;
    }

    public abstract Object clone();

    protected abstract URIParser getParser();

    @Override
    public boolean equals(Object obj) {
        boolean result;
        if (obj instanceof URIBase) {
            final URIBase uri = (URIBase) obj;
            //if (uri == this) return true; //This case will be checked in overrided classes
            result = this.scheme.equals(uri.scheme) && this.parameters.equals(uri.parameters)
                    && checkEquality(this.headers, uri.headers);
        } else
            result = false;
        return result;
    }

    protected final boolean checkEquality(Object obj1, Object obj2) {
        return (obj1 == null && obj2 == null) || (obj1 != null && obj1.equals(obj2));
    }

    protected void cloneInternals(URIBase uriBase) {
        this.scheme = uriBase.scheme;
        this.parameters.putAll(uriBase.parameters);
        if (uriBase.headers != null) {
            this.headers = new HashMap<String, String>(uriBase.headers.size());
            this.headers.putAll(uriBase.headers);
        }
        this.parsed = uriBase.parsed;
        if (uriBase.data != null) {
            this.data = new byte[uriBase.end - uriBase.start];
            System.arraycopy(uriBase.data, uriBase.start, this.data, 0, uriBase.end - uriBase.start);
            this.start = uriBase.start;
            this.end = uriBase.end;
        } else {
            this.data = null;
            this.start = this.end = 0;
        }
    }

    public String getParameter(String name) {
        parse();
        return parameters.get(name);
    }

    public void setParameter(String name, String value) throws ParseException {
        invalidateStringValue();
        parameters.put(name, value);
    }

    public Iterator getParameterNames() {
        parse();
        return parameters.keySet().iterator();
    }

    protected String getParametersAsString(String delimiter) {
        final StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : parameters.entrySet()) {
            sb.append(delimiter).append(entry.getKey());
            final String value = entry.getValue();
            if (value != null && !value.equals(""))
                sb.append('=').append(value);
        }
        return sb.toString();
    }

    public void removeParameter(String name) {
        invalidateStringValue();
        parameters.remove(name);
    }

    public boolean hasParameter(String name) {
        parse();
        return parameters.containsKey(name);
    }

    /**
     * Returns the value of the named header, or null if it is not set.
     * SIP/SIPS URIs may specify headers. As an example, the URI
     * sip:jimmy@jcp.org?priority=urgent has a header "priority" whose
     * value is "urgent".
     *
     * @param name name of header to retrieve.
     * @return the value of specified header.
     */
    public String getHeader(String name) {
        parse();
        String value = headers == null ? null : headers.get(name);
        if (value != null) value = URLDecoder.decode(value);
        return value;
    }

    /**
     * Sets the value of the specified header fields to be included in a
     * request constructed from the URI. If the header already had a value it
     * will be overwritten.
     *
     * @param name  a String specifying the header name.
     * @param value a String specifying the header value.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the name or value parameters.
     */
    public void setHeader(String name, String value) throws ParseException {
        invalidateStringValue();
        if (headers == null) headers = new HashMap<String, String>();
        headers.put(name, URLEncoder.encode(value));
    }

    /**
     * Returns an Iterator over the String names of all headers present
     * in this SipURI.
     *
     * @return an Iterator over all the header names.
     */
    public Iterator getHeaderNames() {
        parse();
        return headers == null ? Collections.EMPTY_LIST.iterator() : headers.keySet().iterator();
    }

    protected String getHeadersAsString() {
        String result = "";
        if (headers != null && headers.size() != 0) {
            final StringBuilder sb = new StringBuilder();
            sb.append('?');
            String lmt = "";
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                sb.append(lmt).append(entry.getKey());
                final String value = entry.getValue();
                if (value != null)
                    sb.append('=').append(value);
                lmt = "&";
            }
            result = sb.toString();
        }
        return result;
    }

    protected final void parse() {
        if (!parsed) doParse();
    }

    protected void doParse() {
        try {
            parsed = true;
            getParser().parse(data, start, end, this);
        } catch (ParseException e) {
        }
    }

    /**
     * Indicates that the cached string representation of the URI is invalid. That method is useful when
     * the contents of the URI are changed.
     */
    protected final void invalidateStringValue() {
        stringValue = null;
    }

    protected abstract String buildStringValue();

    public String toString() {
        if (stringValue == null) {
            parse();
            stringValue = buildStringValue();
        }
        return stringValue;
    }
}
/*
 * $Log$
 */